#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "psd_session.h"
#include "psd_modules.h"
#include "psd_measurement.h"
#include "psd_config.h"
#include "psd_logger.h"
#include "psd_event.h"
#include "psd_stats.h"
#include "psd_auth.h"

#include "compat.h"

psdModule *module_info();
int psd_stats_init();
static void psd_stats_read_config();
static int psd_stats_session_end_cb(psdSess *sess);
static int psd_stats_conn_end_cb(psdConn *conn);

psdStatsBackend *stats_be;
pthread_mutex_t stats_be_lock;

static psdModule psd_stats_module = {
	.desc = "Statistics Collection Module",
	.dependencies = "",
	.init = psd_stats_init
};

psdModule *module_info() {
	return &psd_stats_module;
}

struct psd_stats_config_t {
	char *backend;
};

static struct psd_stats_config_t psdStatsConfig = {
	.backend = "sqlite",
};

static void psd_stats_read_config() {
	char *str_val;

	if (psd_depot_settings_get("stats", "backend", &str_val) == 0) {
		psdStatsConfig.backend = str_val;
	}
}

int psd_stats_init() {
	char module_name[255];

	psd_stats_read_config();

	if (pthread_mutex_init(&stats_be_lock, NULL)) {
		psd_err(0, "couldn't initialize mutex");
		goto error_exit;
	}

	strlcpy(module_name, "stats_", sizeof(module_name));
	strlcat(module_name, psdStatsConfig.backend, sizeof(module_name));

	if (psd_load_module(module_name) != 0) {
		psd_err(0, "couldn't load backend: %s", psdStatsConfig.backend);
		goto error_exit2;
	}

	if (stats_be == NULL) {
		psd_err(0, "backend didn't register itself: %s", psdStatsConfig.backend);
		goto error_exit2;
	}

	//if (register_listener_session_end(psd_stats_session_end_cb)) {
	//	psd_err(0, "couldn't register stats callback");
	//	goto error_exit2;
	//}

	return 0;

error_exit2:
	pthread_mutex_destroy(&stats_be_lock);
error_exit:
	return -1;
}

int psd_set_stats_backend(psdStatsBackend *be) {
	pthread_mutex_lock(&stats_be_lock);
	{
		stats_be = be;
	}
	pthread_mutex_unlock(&stats_be_lock);

	return 0;
}

psdSessionStats **psd_stats_get_session_stats_history(double earliest_time, int max_count, int *num_stats) {
	psdSessionStats **ret_stats = NULL;
	int ret_count = 0;

	pthread_mutex_lock(&stats_be_lock);
	{
		if (stats_be->read_stats) {
			ret_stats = stats_be->read_stats(earliest_time, max_count, &ret_count);
		} else {
			ret_count = 0;
			ret_stats = NULL;
		}
	}
	pthread_mutex_unlock(&stats_be_lock);

	*num_stats = ret_count;

	return ret_stats;
}

int psd_session_save_stats(psdSessionStats *stats) {
	int retval;

	pthread_mutex_lock(&stats_be_lock);
	{
		if (stats_be->save_stats)
			retval = stats_be->save_stats(stats);
		else
			retval = -1;
	}
	pthread_mutex_unlock(&stats_be_lock);

	return retval;
}

psdSessionStats *psd_session_stats_alloc() {
	psdSessionStats *stats;

	stats = malloc(sizeof(psdSessionStats));
	if (!stats)
		goto error_exit;

	bzero(stats, sizeof(psdSessionStats));

	return stats;

error_exit:
	return NULL;
}

void psd_session_stats_free(psdSessionStats *stats) {
	if (stats->username)
		free(stats->username);
	if (stats->session_id)
		free(stats->session_id);
	if (stats->email)
		free(stats->email);
	if (stats->institution)
		free(stats->institution);
	if (stats->upstream_conns)
		free(stats->upstream_conns);
	if (stats->downstream_conns)
		free(stats->downstream_conns);
	free(stats);
}

psdConnStats *psd_conn_stats_alloc() {
	psdConnStats *stats;

	stats = malloc(sizeof(psdConnStats));
	if (!stats)
		goto error_exit;

	bzero(stats, sizeof(psdConnStats));

	return stats;

error_exit:
	return NULL;
}

void psd_conn_stats_free(psdConnStats *stats) {
	if (stats->session_id)
		free(stats->session_id);
	if (stats->destination)
		free(stats->destination);
	free(stats);
}

static int psd_stats_conn_end_cb(psdConn *conn) {
	uint64_t bytes_written;
	size_t bytes_written_size;
	uint64_t bytes_read;
	size_t bytes_read_size;
	double timelen;
	psdConnStats stats;

	if (conn->session != NULL)
		stats.session_id = conn->session->id;
	else
		stats.session_id = "";

	stats.destination = conn->description;

	bytes_written_size = sizeof(bytes_written);
	if (psd_conn_get_stat(conn, PSD_STATS_BYTES_WRITTEN, &bytes_written, &bytes_written_size) != 0) {
		bytes_written = 0;
	}
	stats.bytes_written = bytes_written;

	bytes_read_size = sizeof(bytes_read);
	if (psd_conn_get_stat(conn, PSD_STATS_BYTES_READ, &bytes_read, &bytes_read_size) != 0) {
		bytes_read = 0;
	}
	stats.bytes_read = bytes_read;

	stats.start_time = conn->start_time.tv_sec + (conn->start_time.tv_usec/1000000.0);

	timelen = difftv(&(conn->start_time), &(conn->end_time));
	stats.duration = timelen;

	psd_conn_save_stats(&stats);

	return 0;
}

static int psd_stats_session_end_cb(psdSess *sess) {
	uint64_t bytes_written;
	size_t bytes_written_size;
	uint64_t bytes_read;
	size_t bytes_read_size;
	char parents[256];
	char children[256];
	psdConn *curr_conn;
	double timelen;
	psdSessionStats stats;

	stats.session_id = psd_session_get_id(sess);
	stats.username = sess->credentials->get_user(sess->credentials);
	stats.institution = sess->credentials->get_institution(sess->credentials);
	stats.email = sess->credentials->get_email(sess->credentials);

/*
	parents[0] = '\0';
	LIST_FOREACH(curr_conn, &(sess->parent_conns), sess_entries) {
		strlcat(parents, curr_conn->description, sizeof(parents));
		if (curr_conn->next != NULL)
			strlcat(parents, ",", sizeof(parents));
	}
	stats.upstream_conns = parents;

	children[0] = '\0';
	LIST_FOREACH(curr_conn, &(sess->child_conns), sess_entries) {
		strlcat(children, curr_conn->description, sizeof(children));
		if (curr_conn->next != NULL)
			strlcat(children, ",", sizeof(children));
	}
	stats.downstream_conns = children;
*/

	bytes_written_size = sizeof(bytes_written);
	if (psd_session_get_stat(sess, PSD_STATS_BYTES_WRITTEN, &bytes_written, &bytes_written_size) != 0) {
		bytes_written = 0;
	}
	stats.bytes_written = bytes_written;

	bytes_read_size = sizeof(bytes_read);
	if (psd_session_get_stat(sess, PSD_STATS_BYTES_READ, &bytes_read, &bytes_read_size) != 0) {
		bytes_read = 0;
	}
	stats.bytes_read = bytes_read;

	stats.end_time = sess->end_time.tv_sec + (sess->end_time.tv_usec/1000000.0);

	timelen = difftv(&(sess->start_time), &(sess->end_time));
	stats.duration = timelen;

	stats.read_rate = bytes_read/timelen*8/1000/1000;
	stats.write_rate = bytes_written/timelen*8/1000/1000;

	psd_session_save_stats(&stats);

	return 0;
}
